// 版权所有2011 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package multipart

import (
	"bytes"
	"errors"
	"io"
	"math"
	"net/textproto"
	"os"
)

// 如果消息表单
// 数据太大而无法处理，则会通过ReadForm返回ErrMessageTooLarge。
var ErrMessageTooLarge = errors.New("multipart: message too large")

// TODO（adg，bradfitz）：找到一种方法，将这里的DoS预防策略
// 与http包的解析形式统一起来。

// ReadForm解析整个多部分消息，其部分包含“表单数据”的内容处理。
// 它在内存中最多存储maxMemory字节+10MB（为非文件部分保留）
// 。无法存储在内存中的文件部分将以临时文件的形式存储在
// 磁盘上。
// 如果所有非文件部分无法存储在
// 内存中，则返回ErrMessageTooLarge。
func (r *Reader) ReadForm(maxMemory int64) (*Form, error) {
	return r.readForm(maxMemory)
}

func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) {
	form := &Form{make(map[string][]string), make(map[string][]*FileHeader)}
	defer func() {
		if err != nil {
			form.RemoveAll()
		}
	}()

	// 为非文件部分额外预留10 MB。
	maxValueBytes := maxMemory + int64(10<<20)
	if maxValueBytes <= 0 {
		if maxMemory < 0 {
			maxValueBytes = 0
		} else {
			maxValueBytes = math.MaxInt64
		}
	}
	for {
		p, err := r.NextPart()
		if err == io.EOF {
			break
		}
		if err != nil {
			return nil, err
		}

		name := p.FormName()
		if name == "" {
			continue
		}
		filename := p.FileName()

		var b bytes.Buffer

		if filename == "" {
			// 值，以字符串形式存储在内存中
			n, err := io.CopyN(&b, p, maxValueBytes+1)
			if err != nil && err != io.EOF {
				return nil, err
			}
			maxValueBytes -= n
			if maxValueBytes < 0 {
				return nil, ErrMessageTooLarge
			}
			form.Value[name] = append(form.Value[name], b.String())
			continue
		}

		// 文件，存储在内存中或磁盘上
		fh := &FileHeader{
			Filename: filename,
			Header:   p.Header,
		}
		n, err := io.CopyN(&b, p, maxMemory+1)
		if err != nil && err != io.EOF {
			return nil, err
		}
		if n > maxMemory {
			// 太大，写入磁盘和刷新缓冲区
			file, err := os.CreateTemp("", "multipart-")
			if err != nil {
				return nil, err
			}
			size, err := io.Copy(file, io.MultiReader(&b, p))
			if cerr := file.Close(); err == nil {
				err = cerr
			}
			if err != nil {
				os.Remove(file.Name())
				return nil, err
			}
			fh.tmpfile = file.Name()
			fh.Size = size
		} else {
			fh.content = b.Bytes()
			fh.Size = int64(len(fh.content))
			maxMemory -= n
			maxValueBytes -= n
		}
		form.File[name] = append(form.File[name], fh)
	}

	return form, nil
}

// 表单是一个解析的多部分表单。
// 其文件部分存储在内存或磁盘中，
// 并可通过*FileHeader的Open方法访问。
// 其值部分存储为字符串。
// 这两个字段都由字段名键入。
type Form struct {
	Value map[string][]string
	File  map[string][]*FileHeader
}

// RemoveAll删除与表单关联的所有临时文件。
func (f *Form) RemoveAll() error {
	var err error
	for _, fhs := range f.File {
		for _, fh := range fhs {
			if fh.tmpfile != "" {
				e := os.Remove(fh.tmpfile)
				if e != nil && err == nil {
					err = e
				}
			}
		}
	}
	return err
}

// 文件头描述多部分请求的文件部分。
type FileHeader struct {
	Filename string
	Header   textproto.MIMEHeader
	Size     int64

	content []byte
	tmpfile string
}

// Open打开并返回文件头的关联文件。
func (fh *FileHeader) Open() (File, error) {
	if b := fh.content; b != nil {
		r := io.NewSectionReader(bytes.NewReader(b), 0, int64(len(b)))
		return sectionReadCloser{r}, nil
	}
	return os.Open(fh.tmpfile)
}

// 文件是访问多部分消息的文件部分的接口。
// 它的内容可以存储在内存中，也可以存储在磁盘上。
// 如果存储在磁盘上，文件的底层具体类型将是*os。文件
type File interface {
	io.Reader
	io.ReaderAt
	io.Seeker
	io.Closer
}

// helper类型将[]字节转换为文件

type sectionReadCloser struct {
	*io.SectionReader
}

func (rc sectionReadCloser) Close() error {
	return nil
}
